package com.zc.framework.spring.aspect;

import com.google.common.base.Joiner;
import com.zc.framework.cache.redis.annotations.RequestLockable;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

/**
 * Created by zc on 2017/1/20.
 */
public abstract  class AbstractRequestLockInterceptor {


    protected abstract Lock getLock(String key);

    protected abstract boolean tryLock(long waitTime, long leaseTime, TimeUnit unit, Lock lock) throws InterruptedException;

    /**
     * 包的表达式目前还有待优化 TODO
     */
    @Pointcut("execution(* com.chanjet.csp..*(..)) && @annotation(com.chanjet.csp.product.core.annotation.RequestLockable)")
    public void pointcut(){}

    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable{
        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        String targetName = point.getTarget().getClass().getName();
        String methodName = point.getSignature().getName();
        Object[] arguments = point.getArgs();

        if (method != null && method.isAnnotationPresent(RequestLockable.class)) {
            RequestLockable requestLockable = method.getAnnotation(RequestLockable.class);

            String requestLockKey = getLockKey(method,targetName, methodName, requestLockable.key(), arguments);
            Lock lock=this.getLock(requestLockKey);
            boolean isLock = this.tryLock(requestLockable.maximumWaiteTime(),requestLockable.expirationTime(), requestLockable.timeUnit(),lock);
            if(isLock) {
                try {
                    return point.proceed();
                } finally {
                    lock.unlock();
                }
            } else {
                throw new RuntimeException("获取锁资源失败");
            }
        }

        return point.proceed();
    }

    private String getLockKey(Method method,String targetName, String methodName, String[] keys, Object[] arguments) {

        StringBuilder sb = new StringBuilder();
        sb.append("lock.").append(targetName).append(".").append(methodName);

        if(keys != null) {
            String keyStr = Joiner.on(".").skipNulls().join(keys);
            if(!StringUtils.isBlank(keyStr)) {
                LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
                String[] parameters =discoverer.getParameterNames(method);
                ExpressionParser parser = new SpelExpressionParser();
                Expression expression = parser.parseExpression(keyStr);
                EvaluationContext context = new StandardEvaluationContext();
                int length = parameters.length;
                if (length > 0) {
                    for (int i = 0; i < length; i++) {
                        context.setVariable(parameters[i], arguments[i]);
                    }
                }
                String keysValue = expression.getValue(context, String.class);
                sb.append("#").append(keysValue);
            }
        }
        return sb.toString();
    }

}
